/*
 * signal.c
 *
 *  Created on: Jul 3, 2011
 *      Author: makkalot
 */

#include "fibsignal.h"
#include "fibthread.h"
#include "sched.h"

#include <errno.h>
#include <string.h>
#include <unistd.h>

/* Internal function prototypes */

static void signal_delete_handler(int signo);
static int block_signals(const sigset_t *src_set, sigset_t *dst_set);
static int delete_signal_from_proc(int signo);
static int append_signal(sigset_t *dst_set, int signo);
static int unblock_signals(const sigset_t *src_set, sigset_t *dst_set);

/* Global API functions */

int fibthread_kill(fibthread_t thread, int sig){

	fibthread_struct *thread_kill;

	initialize();

	if(sig < 0 || sig > NSIG)
		return EINVAL;

	/* search for thread in all_threads if not there raise error */
	if((thread_kill = get_fibthread_struct_from_id(thread)) == NULL)
		return ESRCH;

	append_signal(&thread_kill->pending_signals, sig);

	return 0;

}

int fibthread_sigmask(int how, const sigset_t *set, sigset_t *oset){

	initialize();

	if(how != SIG_BLOCK && how!= SIG_SETMASK && how != SIG_UNBLOCK)
		return EINVAL;

	if(oset){
		*oset = curr_thread->thread_mask;
		//memcpy((void *)oset, (void *)&curr_thread->thread_mask, sizeof(sigset_t));
	}

	if(set){
		/* Handle set here */
		if(how == SIG_SETMASK)
			curr_thread->thread_mask = *set;
			//memcpy(&curr_thread->thread_mask, set, sizeof(sigset_t));
		else if(how == SIG_BLOCK)
			block_signals(set, &curr_thread->thread_mask);
		else
			unblock_signals(set, &curr_thread->thread_mask);

		/* Now set the mask process wide to have effect */
		if(sigprocmask(SIG_SETMASK, &curr_thread->thread_mask, NULL) < 0)
			return -1;
	}

	return 0;

}


int fibthread_sigwait(const sigset_t *set, int *signal){

	struct timespec timeval;
	int result;

	initialize();

	timeval.tv_sec = 0;
	timeval.tv_nsec = 0;

	while(1){
		result = sigtimedwait(set, NULL, &timeval);
		if(result < 0){
			/* Didnt receive any signal so give the turn to someone else */
			if(fibthread_yield() != 0)
				return EINVAL;
			continue;
		}
		break;
	}

	*signal = result;
	return 0;
}

/* Global functions used internally */
int switch_thread_signal_mask(fibthread_struct *prev_thread, fibthread_struct *next_thread){

	int i;
	sigset_t proc_pending;

	sigemptyset(&proc_pending);
	if(sigpending(&proc_pending) < 0)
		return -1;

	/* Before make the switch we should check if we delivered the thread specific ones*/
	/* Before make real signal switch we should remove the signals that
		 * were raised for thread_specific signals(for prev). Those signals should not
		 * pass to other new thread, because they may terminate the process */

	for (i = 1; i < NSIG; ++i) {
		if(sigismember(&prev_thread->pending_signals, i)){
			if(!sigismember(&proc_pending, i)){
				/* That means we have delivered it so delete it */
				sigdelset(&prev_thread->pending_signals, i);
			}
			else{
				/* In that case it is still pending and it is thread specific so delete it */
				delete_signal_from_proc(i);
			}
		}
	}

	/* Now make the signal switch here */
	if(sigprocmask(SIG_SETMASK, &next_thread->thread_mask, NULL) < 0)
		return -1;

	/* Now raise all that signals that are specific to that new thread */
	sigpending(&proc_pending);
	for(i = 1; i < NSIG; i++){
		if(sigismember(&next_thread->pending_signals, i))
			if(!sigismember(&proc_pending,i))
				kill(getpid(),i);
	}

	return 0;
}

/* Internal API functions */

static void signal_delete_handler(int signo){
	/* nothing here just created to delete pending signal */
	return;
}

static int block_signals(const sigset_t *src_set, sigset_t *dst_set){

	int i;
	for(i = 1; i< NSIG; i++)
		if(sigismember(src_set, i) && !sigismember(dst_set, i))
			sigaddset(dst_set, i);

	return 0;
}



static int delete_signal_from_proc(int signo){

	struct sigaction old_siga, new_siga;
	sigset_t old_set, new_set;
	sigset_t proc_pending;

	if(sigpending(&proc_pending) < 0)
		return -EINVAL;

	if(!sigismember(&proc_pending, signo))
		return -1;

	/* now add signal to mask of process and save the old*/
	sigemptyset(&new_set);
	sigaddset(&new_set, signo);
	if(sigprocmask(SIG_BLOCK, &new_set, &old_set))
		return -1;

	/* register dummy handler for signo and save old */
	new_siga.sa_handler = signal_delete_handler;
	sigfillset(&new_siga.sa_mask);
	new_siga.sa_flags = 0;

	if(sigaction(signo, &new_siga, &old_siga) < 0){
		sigprocmask(SIG_SETMASK, &old_set, NULL);
		return -1;
	}

	/* wait for signal with sigsuspend to occur */
	sigfillset(&new_set);
	sigdelset(&new_set, signo);
	sigsuspend(&new_set);

	/* restore old sigmask */
	if(sigaction(signo, &old_siga, NULL) < 0){
		sigprocmask(SIG_SETMASK, &old_set, NULL);
		return -1;
	}

	/* restore old sigaction */
	sigprocmask(SIG_SETMASK, &old_set, NULL);

	return 0;
}

static int append_signal(sigset_t *dst_set, int signo){

	if(!sigismember(dst_set, signo))
		sigaddset(dst_set, signo);

	return 0;
}

static int unblock_signals(const sigset_t *src_set, sigset_t *dst_set){

	int i;
	for(i = 1; i< NSIG; i++)
		if(sigismember(src_set, i) && sigismember(dst_set, i))
			sigdelset(dst_set, i);

	return 0;
}
